/**
 * @file    elog_file.c
 * @brief   EasyLogger文件插件实现
 * @details 本文件实现了EasyLogger文件存储插件的核心功能，包括：
 *          - 基于文件系统的日志存储功能
 *          - 日志文件轮转和大小限制管理
 *          - 文件日志的写入、配置和清理
 *          - 文件存储锁定控制和移植层接口
 *          - 插件的初始化和反初始化流程
 *          - 多文件备份和自动轮转机制
 *          
 *          功能特性：
 *          - 文件轮转：当日志文件达到最大大小时自动创建新文件
 *          - 备份管理：支持多个备份文件，自动删除过期文件
 *          - 缓存控制：可选的文件缓存刷新功能，确保数据及时写入
 *          - 线程安全：提供文件锁定机制保护并发访问
 *          - 灵活配置：支持文件名、大小、轮转数量的动态配置
 *          - 错误处理：完善的错误检查和异常处理机制
 *          
 *          文件轮转机制：
 *          - xxx.log -> xxx.log.0 (当前日志文件轮转为备份文件)
 *          - xxx.log.0 -> xxx.log.1 (备份文件依次向后轮转)
 *          - xxx.log.n-1 -> xxx.log.n (最老的备份文件被删除)
 *          
 *          支持的文件系统：FATFS、POSIX兼容文件系统
 */

#define LOG_TAG    "elog.file"    /* 日志标签 */

/* ----------------------------------- 包含文件 -------------------------------------- */
#include <stdio.h>      /* 标准输入输出库 */
#include <stdbool.h>    /* 布尔类型定义 */
#include <stdlib.h>     /* 标准库函数 */
#include <string.h>     /* 字符串处理函数 */

#include "elog_file.h"  /* 文件插件头文件 */

#ifdef ELOG_FILE_ENABLE

/* ----------------------------------- 全局变量定义 ----------------------------------- */
static bool init_ok = false;        /* 初始化完成标志 */
static FILE *fp = NULL;             /* 日志文件指针 */
static ElogFileCfg local_cfg;       /* 本地配置结构体 */

/**
 * @brief   初始化文件插件
 * @return  错误代码
 * @retval  ELOG_NO_ERR 初始化成功
 * @note    该函数会初始化移植层接口并配置默认参数
 */
ElogErrCode elog_file_init(void)
{
    ElogErrCode result = ELOG_NO_ERR;
    ElogFileCfg cfg;

    /* 检查是否已经初始化 */
    if (init_ok)
        goto __exit;

    /* 初始化移植层接口 */
    elog_file_port_init();

    /* 配置默认参数 */
    cfg.name = ELOG_FILE_NAME;
    cfg.max_size = ELOG_FILE_MAX_SIZE;
    cfg.max_rotate = ELOG_FILE_MAX_ROTATE;

    /* 应用配置 */
    elog_file_config(&cfg);

    /* 设置初始化完成标志 */
    init_ok = true;
    
__exit:
    return result;
}

/**
 * @brief   轮转日志文件
 * @return  轮转结果
 * @retval  true  轮转成功
 * @retval  false 轮转失败
 * @note    将xxx.log.n-1重命名为xxx.log.n，xxx.log重命名为xxx.log.0
 */
static bool elog_file_rotate(void)
{
#define SUFFIX_LEN                     10
    int n, err = 0;
    char oldpath[256]= {0}, newpath[256] = {0};
    size_t base = strlen(local_cfg.name);
    bool result = true;
    FILE *tmp_fp;

    /* 复制基础文件名到路径缓冲区 */
    memcpy(oldpath, local_cfg.name, base);
    memcpy(newpath, local_cfg.name, base);

    /* 关闭当前文件 */
    fclose(fp);

    /* 从最大轮转数开始，逐个重命名文件 */
    for (n = local_cfg.max_rotate - 1; n >= 0; --n) 
    {
        /* 构造旧文件名和新文件名 */
        snprintf(oldpath + base, SUFFIX_LEN, n ? ".%d" : "", n - 1);
        snprintf(newpath + base, SUFFIX_LEN, ".%d", n);
        
        /* 删除目标文件（如果存在） */
        if ((tmp_fp = fopen(newpath , "r")) != NULL) 
        {
            fclose(tmp_fp);
            remove(newpath);
        }
        
        /* 重命名源文件到目标文件 */
        if ((tmp_fp = fopen(oldpath , "r")) != NULL) 
        {
            fclose(tmp_fp);
            err = rename(oldpath, newpath);
        }

        /* 检查重命名操作是否成功 */
        if (err < 0) 
        {
            result = false;
            goto __exit;
        }
    }

__exit:
    /* 重新打开主日志文件 */
    fp = fopen(local_cfg.name, "a+");

    return result;
}


/**
 * @brief   写入日志到文件
 * @param   log  日志内容
 * @param   size 日志长度
 * @note    当文件大小超过限制时会自动进行轮转
 */
void elog_file_write(const char *log, size_t size)
{
    size_t file_size = 0;

    /* 参数有效性检查 */
    ELOG_ASSERT(init_ok);
    ELOG_ASSERT(log);
    if(fp == NULL) 
    {
        return;
    }

    /* 获取文件锁 */
    elog_file_port_lock();

    /* 获取当前文件大小 */
    fseek(fp, 0L, SEEK_END);
    file_size = ftell(fp);

    /* 检查是否需要轮转文件 */
    if (unlikely(file_size > local_cfg.max_size)) 
    {
#if ELOG_FILE_MAX_ROTATE > 0
        /* 执行文件轮转 */
        if (!elog_file_rotate()) 
        {
            goto __exit;
        }
#else
        /* 不支持轮转，直接退出 */
        goto __exit;
#endif
    }

    /* 写入日志内容 */
    fwrite(log, size, 1, fp);

#ifdef ELOG_FILE_FLUSH_CACHE_ENABLE
    /* 立即刷新缓存到磁盘 */
    fflush(fp);
#endif

__exit:
    /* 释放文件锁 */
    elog_file_port_unlock();
}

/**
 * @brief   反初始化文件插件
 * @note    关闭文件并清理资源
 */
void elog_file_deinit(void)
{
    ELOG_ASSERT(init_ok);

    ElogFileCfg cfg = {NULL, 0, 0};

    /* 清空配置并关闭文件 */
    elog_file_config(&cfg);

    /* 反初始化移植层接口 */
    elog_file_port_deinit();

    /* 清除初始化标志 */
    init_ok = false;
}

/**
 * @brief   配置文件插件参数
 * @param   cfg 配置结构体指针，为NULL时清空配置
 * @note    会关闭当前文件并根据新配置重新打开
 */
void elog_file_config(ElogFileCfg *cfg)
{
    /* 获取文件锁 */
    elog_file_port_lock();

    /* 关闭当前打开的文件 */
    if (fp) 
    {
        fclose(fp);
        fp = NULL;
    }

    /* 应用新配置 */
    if (cfg != NULL) 
    {
        /* 更新本地配置 */
        local_cfg.name = cfg->name;
        local_cfg.max_size = cfg->max_size;
        local_cfg.max_rotate = cfg->max_rotate;

        /* 打开新的日志文件 */
        if (local_cfg.name != NULL && strlen(local_cfg.name) > 0)
            fp = fopen(local_cfg.name, "a+");
    }

    /* 释放文件锁 */
    elog_file_port_unlock();
}

#endif /* ELOG_FILE_ENABLE */
